plugins: VarLag fix
[supercollider.git] / examples / GUI examples / analog-drum-tuner.scd
blob97421df8b9dbd9cb6a9b9b1205a8412c717402d4
1 // Ringz tuner - h. james harkins, http://www.dewdrop-world.net
2 // Makes analog-style drum sounds with a bank of tunable Ringz filters
3 // code is released under the LGPL, http://creativecommons.org/licenses/LGPL/2.1/
5 // optional
6 // Server.default = s = Server.internal;
8 s.boot;
10 // double-click inside the parenthesis, then hit enter to evaluate
13 var     percBus, percGroup, ringzBus, ringzGroup, trigbus, trigsynth, src, toStereo,
14         levelBus, levelEditor, toStereoOut,
15         nodes, ffreqs, rqs, decays, amps,
16         log001, sr, dt,
17         freqspec, rqspec, ampspec,
18         noiseString = "PinkNoise.ar", envString = "Env.perc(0.01, 0.2)",
19         
20         post, buildSynthDef, currentDef, g, stop,
21         
22         window, leftview, rightview, ffreqview, rqview, ampview, rateEdit, newButton,
23         funcedit, envedit, helpview;
25 // preparation
26 percBus = Bus.audio(s, 1);
27 ringzBus = Bus.audio(s, 1);
28 percGroup = Group.new;
29 ringzGroup = Group.after(percGroup);
30 levelBus = Bus.control(s, 1).set(0.75);
31 toStereoOut = SynthDef('1to2', { |bus, level|
32         Out.ar(0, (In.ar(bus, 1) * level) ! 2)
33 }).play(ringzGroup, [bus: ringzBus, level: levelBus.asMap], addAction: \addAfter);
35 SynthDef(\ringz, { |ffreq = 20, decay = 0.01, in, out, amp = 0|
36         Out.ar(out, Ringz.ar(In.ar(in, 1), ffreq, decay, amp))
37 }).send(s);
39 nodes = Array.new(10);
40 ffreqs = [];
41 rqs = [];
42 amps = [];
43 decays = dt.(ffreqs, rqs);
44 log001 = log(0.001);    // constant, will be used repeatedly
46 // func to calc decay time from freq and rq
47 sr = s.sampleRate;
48 dt = { |ffreq, rq|
49         (log001 / log(1 - (pi/sr * ffreq * rq))) / sr;
51 freqspec = \freq.asSpec;
52 rqspec = [1, 0.001, \exp].asSpec;
53 ampspec = \amp.asSpec;
55 // gui building
56 window = MultiPageLayout("Ringz tuner", Rect(150, 5, 800, 570));
57 leftview = FlowView(window, 470@490);
58 rightview = FlowView(window, 280@490);
60 // left-hand side: filters
61 GUI.staticText.new(leftview, 150@20).string_("frequencies");
62 GUI.staticText.new(leftview, 150@20).string_("resonances");
63 GUI.staticText.new(leftview, 150@20).string_("amplitudes");
64 ffreqview = GUI.multiSliderView.new(leftview, 150@200)
65         .value_(freqspec.unmap(ffreqs))
66         .thumbSize_(14)
67         .action_({ |v|
68                 ffreqs = freqspec.map(v.value);
69                 decays = dt.(ffreqs, rqs);
70                 nodes.do({ |n, i|
71                         n.set(\ffreq, ffreqs[i], \decay, decays[i])
72                 })
73         });
74 rqview = GUI.multiSliderView.new(leftview, 150@200)
75         .value_(rqspec.unmap(rqs))
76         .thumbSize_(14)
77         .action_({ |v|
78                 rqs = rqspec.map(v.value);
79                 decays = dt.(ffreqs, rqs);
80                 nodes.do({ |n, i|
81                         n.set(\decay, decays[i]);
82                 })
83         });
84 ampview = GUI.multiSliderView.new(leftview, 150@200)
85         .value_(ampspec.unmap(amps))
86         .thumbSize_(14)
87         .action_({ |v|
88                 amps = ampspec.map(v.value);
89                 nodes.do({ |n, i| n.set(\amp, amps[i]) })
90         });
92 rateEdit = NumberEditor(2, [0.5, 10]).action_({ |val| trigsynth.set(\trigrate, val) });
93 levelEditor = NumberEditor(0.75, \amp).action_({ |val| levelBus.set(val) });
95 leftview.startRow;
96 GUI.staticText.new(leftview, 50@20).string_("trigrate").align_(\right);
97 rateEdit.gui(leftview);
98 GUI.staticText.new(leftview, 50@20).string_("volume").align_(\right);
99 levelEditor.gui(leftview);
101 // ActionButtons -- add filter, post parameters, stop, save, load
102 post = {
103         (nodes.size > 0).if({
104                 postf("\nKlank array:\n`[ %,\n   %,\n   % ]\n\n",
105                         ffreqs, //[..nodes.size-1],
106                         amps, // \amp.asSpec.map(ampview.value[..nodes.size-1]),
107                         decays); // [..nodes.size-1];
108                 [ffreqs, decays, amps]
109                         .flop/*[..nodes.size-1]*/.do({ |item|
110                                 postf("[\\ffreq, %, \\decay, %, \\amp, %]\n", *item);
111                 });
112         });
115 leftview.startRow;
116 newButton = ActionButton(leftview, "new resonz", {
117         if(nodes.size < 10) {
118                 ffreqs = ffreqs.add(20);
119                 rqs = rqs.add(1);
120                 decays = dt.(ffreqs, rqs);
121                 amps = amps.add(0);
122                 ffreqview.value = freqspec.unmap(ffreqs);
123                 rqview.value = rqspec.unmap(rqs);
124                 ampview.value = ampspec.unmap(amps);
125 //              nodes.add(ringzMixer.play(\ringz, [\in, percMixer.inbus.index]));
126                 nodes.add(Synth(\ringz, [\in, percBus, \out, ringzBus], ringzGroup,
127                         addAction: \addToTail));
128         };
130 ActionButton(leftview, "post parameters", post);
132 stop = {
133         post.value;
134         toStereoOut.free;
135         src.free;
136         nodes.do(_.free);
137         trigbus.free;
138         percBus.free; percGroup.free;
139         ringzBus.free; ringzGroup.free;
140         levelBus.free;
141 //      percMixer.free; ringzMixer.free;
142         if(window.isClosed.not) { window.onClose_(nil).close };
145 ActionButton(leftview, "stop", stop);
147 leftview.startRow;
148 ActionButton(leftview, "save", {
149         GUI.dialog.savePanel({ |path|
150                 [ffreqview.value, rqview.value, ampview.value,
151                         noiseString, envString, nodes.size]
152                 .writeTextArchive(path)
153         });
156 ActionButton(leftview, "load", {
157         var     values;
158         GUI.dialog.getPaths({ |path|
159                 values = Object.readTextArchive(path[0]);
160                 (values.size != 6).if({
161                         Error("File does not contain a ringz spec.").throw;
162                 }, {
163                         noiseString = values[3];
164                         funcedit.setString(noiseString, 0, funcedit.string.size);
165                         envString = values[4];
166                         envedit.setString(envString, 0, envedit.string.size);
167                         buildSynthDef.value;
168                         ffreqview.value = values[0];
169                         rqview.value = values[1];
170                         ampview.value = values[2];
171                         nodes.do(_.free);
172                         nodes = { |i|
173                                 Synth(\ringz, [\in, percBus, \out, ringzBus], ringzGroup,
174                                         addAction: \addToTail);
175 //                              ringzMixer.play(\ringz, [\in, percMixer.inbus.index]);
176                         } ! values[5];  // make the nodes
177                         {       ffreqview.doAction;
178                                 rqview.doAction;
179                                 ampview.doAction;
180                         }.defer(0.2);
181                 });
182         }, nil, 1)
186 // dynamic synthdef construction based on user input
187 GUI.staticText.new(rightview, 100@20).string_("Noise function:");
188 ActionButton(rightview, "evaluate", { |view|
189         noiseString = funcedit.string;
190         buildSynthDef.value;
192 rightview.startRow;
193 funcedit = GUI.textView.new(rightview, 275@200)
194         .string_(noiseString);
196 rightview.startRow;
197 GUI.staticText.new(rightview, 100@20).string_("Envelope:");
198 ActionButton(rightview, "evaluate", { |view|
199         envString = envedit.string;
200         src.setn(\env, envString.interpret.asArray);
202 rightview.startRow;
203 envedit = GUI.textView.new(rightview, 275@200)
204         .string_("Env.perc(0.01, 0.2)");
206 trigbus = Bus.control(s, 1);
207 {       trigsynth = SynthDef(\percTrig, { |trigrate, out|
208                 Out.kr(out, Impulse.kr(trigrate))
209         }).play(percGroup, [\trigrate, rateEdit.value, \out, trigbus], addAction: \addToHead);
210 }.defer(0.2);
212 buildSynthDef = {
213         var     func, env;
214         currentDef.notNil.if({
215                 s.sendMsg(\d_free, currentDef.name);
216         });
217         func = ("{ " ++ noiseString ++ " }").interpret;
218         env = envString.interpret;
219         currentDef = SynthDef(\source, { |trigbus, outbus|
220                 var     trig, sig, envCtl;
221                 trig = In.kr(trigbus, 1);
222 //              Out.kr(trigbus, trig);
223                 envCtl = Control.names(\env).kr(0 ! 100);       // allow 25 env nodes maximum
224                 sig = SynthDef.wrap(func, nil, [trig]) * EnvGen.ar(envCtl, trig);
225                 Out.ar(outbus, sig)
226         }).send(s);
227         {       src.free;
228                 src = Synth(\source, [\trigbus, trigbus, \outbus, percBus], percGroup,
229                         addAction: \addToTail);
230                 src.setn(\env, env.asArray);
231         }.defer(0.1);
234 helpview = GUI.textView.new(leftview, 458@161);
235 if(GUI.scheme.name == 'CocoaGUI') {
236         helpview.font = GUI.font.new("Helvetica", 12);
238 helpview.string = "Analog percussion tuner by James Harkins
240 Move the black knobs up and down to change the resonator characteristics.
242 The noise function and envelope control the exciter -- change the code and click 'evaluate' to hear it. Click 'new resonz' to add another filter (up to 10). Filters run in parallel, not in series.
244 The 'save' button stores the specs into a file; 'load' returns the editor to a saved state. 'stop' closes the window and removes all synths.";
245 helpview.editable = false;
247 window.onClose = stop;
250         buildSynthDef.value;
251         newButton.doAction;
252         window.front;
253 }.defer(0.5);